const PENDDING = 'pendding'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MPromise {
  FULFILLED_CALLBACK_LIST = []
  REJECTED_CALLBACK_LIST = []
  _status = PENDDING
  constructor(fn) {
    this.value = null
    this.reason = null
    this.status = PENDDING
    try {
      fn(this.resolve.bind(this), this.reject.bind(this))
    } catch (error) {
      this.reject(error)
    }
  }
  get status() {
    return this._status
  }

  set status(newStatus) {
    this._status = newStatus
    switch (newStatus) {
      case FULFILLED: {
        this.FULFILLED_CALLBACK_LIST.forEach(cb => cb(this.value))
        break
      }
      case REJECTED: {
        this.REJECTED_CALLBACK_LIST.forEach(cb => cb(this.reason))
        break
      }
    }
  }

  resolve(value) {
    if (this.status === PENDDING) {
      this.value = value
      this.status = FULFILLED
    }
  }

  reject(reason) {
    if (this.status === PENDDING) {
      this.reason = reason
      this.status = REJECTED
    }
  }

  then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value => value
    const realOnRejected = this.isFunction(onRejected) ? onRejected : reason => reason

    const promise2 = new MPromise((resolve, reject) => {
      const fulfilledMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnFulfilled(this.value)
            this.resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }
      const rejectedMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnRejected(this.reason)
            this.resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }
      switch (this.status) {
        case FULFILLED: {
          fulfilledMicrotask()
          break
        }
        case REJECTED: {
          rejectedMicrotask()
          break
        }
        case PENDDING: {
          this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
          this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
          break
        }
      }
    })

    return promise2

  }
  catch(onRejected) {
    this.then(null, onRejected)
  }
  resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
      return reject(new TypeError('the promise and return value ane the same'))
    }
    if (x instanceof MPromise) {
      queueMicrotask(() => {
        x.then((y) => {
          this.resolvePromise(promise2, y, resolve, reject)
        }, reject)
      })
    } else if (typeof x === 'object' || this.isFunction(x)) {
      if (x === null) {
        return resolve(x)
      }
      let then
      try {
        then = x.then
      } catch (error) {
        return reject(error)
      }
      if (this.isFunction(then)) {
        let called = false
        try {
          then(
            x,
            (y) => {
              if (called) return
              called = true
              this.resolvePromise(promise2, y, resolve, reject)
            },
            (r) => {
              if (called) return
              called = true
              reject(r)
            }
          )
        } catch (error) {
          if (called) {
            return
          }
          reject(error)
        }
      } else {
        resolve(x)
      }
    } else {
      resolve(x)
    }

  }

  isFunction(fn) {
    return typeof fn === 'function'
  }

  static resolve(value) {
    if (value instanceof MPromise) {
      return value
    }
    return new MPromise((resolve, reject) => {
      resolve(value)
    })
  }
  static reject(reason){
    return new MPromise((resolve, reject)=>{
      reject(reason)
    })
  }

  static race (promiseList){
    return new MPromise((resolve, reject)=>{
      const leng = promiseList.length
      if (leng == 0) {
        resolve() 
      } else {
        for (let i=0;i<leng;i++) {
          MPromise.resolve(promiseList[i]).then(value=>{
            resolve(value)
          },reason=>{
            reject(reason)
          })
        }
      }

    })
  }
}

const p11 = MPromise.resolve(999)

const p1 = new MPromise((resolve, reject) => {
  setTimeout(() => {
    // reject(111)
    resolve(222)
  }, 2000)
})

const race1 = MPromise.race([p1,'bbb',p11,'aaa']).then(res=>{
  console.log('race1',res)
})

// const p1 = new MPromise((resolve, reject) => {
//   setTimeout(() => {
//     // reject(111)
//     resolve(222)
//   }, 2000)
// }).then(res => {
//   console.log('then', res)
// }, r => {
//   console.log('reject', r)
// }).catch(e => {
//   console.log('catch', e)
// })